from collections import namedtuple

import sys
import re

# name implies direction
Port = namedtuple('Port', ['name', 'width'])

class Bundle:

    def __init__(self, name, wire):
        self.name = name
        self.width = wire.width
        self.wires = [wire.name]

    def add_wire(self, wire):
        self.width += wire.width
        self.wires.append(wire.name)

re_module_decl = re.compile(r'module\s+(\w+)')
re_port = re.compile(r'\s*(input|output)\s*(\[\s*(\d+):\s*(\d+)\]\s*)?(\w+)')

def parse_module_interface(filepath):
    ports = []
    with open(filepath) as f:
        num_lines_read = 0
        match = None
        while not match:
            if num_lines_read == 10:
                raise ValueError('no module declaration found in first 10 lines')
            line = f.readline()
            num_lines_read += 1
            if line == '':
                raise ValueError('EOF while looking for module declaration')
            match = re_module_decl.match(line)
        module_name = match.group(1)
        while True:
            line = f.readline()
            num_lines_read += 1
            if line == '':
                raise ValueError('EOF while parsing port declarations')
            if line[0] == ')':
                break
            match = re_port.match(line)
            if match:
                if match.group(2) is not None:
                    hi = int(match.group(3))
                    lo = int(match.group(4))
                    width = hi-lo+1
                else:
                    width = 1
                name = match.group(5)
                direction = match.group(1)
                if direction == 'input':
                    if not name.startswith('i_'):
                        raise ValueError('input port name must start with ‘i_’: ' + name)
                else: # output
                    if not name.startswith('o_'):
                        raise ValueError('output port name must start with ‘o_’: ' + name)
                ports.append(Port(name, width))
    return (module_name, ports)

def decorate_port_name(name):
    assert name[0] in 'io' and name[1] == '_'
    return name[2:] + '_' + name[0]

def main():
    for infilepath in sys.argv[1:]:
        module_name, ports = parse_module_interface(infilepath)
        wrapper_module_name = module_name + '_wrapper'
        bundles = []
        bundle_map = {}
        for port in ports:
            segments = port.name.split('_', 2)
            bundle_name = '_'.join(segments[:2])
            if bundle_name not in bundle_map:
                bundle = Bundle(bundle_name, port)
                bundles.append(bundle)
                bundle_map[bundle_name] = bundle
            else:
                bundle_map[bundle_name].add_wire(port)
        # write output file
        with open(wrapper_module_name + '.v', 'w') as outfile:
            outfile.write('module %s(%s);\n' % (wrapper_module_name, ', '.join(map(lambda bundle: bundle.name, bundles))))
            # port declarations
            for bundle in bundles:
                if bundle.name[0] == 'i':
                    outfile.write('input ')
                elif bundle.name[0] == 'o':
                    outfile.write('output ')
                else:
                    assert False
                if bundle.width > 1:
                    outfile.write('[%d:0] ' % (bundle.width-1))
                outfile.write('%s;\n' % bundle.name)
            # wires
            for port in ports:
                outfile.write('wire ')
                if port.width > 1:
                    outfile.write('[%d:0] ' % (port.width-1))
                outfile.write('%s;\n' % decorate_port_name(port.name))
            # assignments
            for bundle in bundles:
                s = ', '.join(map(decorate_port_name, bundle.wires))
                if bundle.name[0] == 'i':
                    outfile.write('assign {%s} = %s;\n' % (s, bundle.name))
                elif bundle.name[0] == 'o':
                    outfile.write('assign %s = {%s};\n' % (bundle.name, s))
                else:
                    assert False
            # the wrapped module
            outfile.write('{0} u_{0}(\n'.format(module_name))
            num_ports = len(ports)
            for index, port in enumerate(ports):
                outfile.write('  .%s(%s)' % (port.name, decorate_port_name(port.name)))
                if index != num_ports-1:
                    outfile.write(',')
                outfile.write('\n')
            outfile.write(');\n')
            outfile.write('endmodule\n')

if __name__ == "__main__":
    main()
